import datetime
import time

from em_logger import logger as elog
from emmodels.em_base_trade_his2 import BaseTradeHis,MyOwnStock
from emutils import stk_utils
from emmongo import em_mongomanager as mongoManager
from LSTM.variableset import EMVar
from dateutil.parser import parse
import numpy as np
class Assets(object):

    def __init__(self, init_assets,strategy_name='AI-',strategy_detail='', own_stock={}, yj_pct=0.0005, **kwargs):
        self.init_assets = init_assets
        self.strategy_name = strategy_name
        self.strategy_detail = strategy_detail
        self.cur_assets = init_assets
        self.pre_assets = init_assets
        self.high_assets = init_assets
        self.low_assets = init_assets
        self.own_stock = own_stock
        self.stock_assets = 0
        self.rest_assets = self.cur_assets - self.stock_assets
        self.create_time = datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HM)
        self.lastupdate_time = datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HM)
        self.trade_times = 0
        self.win_times = 0
        self.loss_times = 0
        self.ping_times = 0
        self.mark_trades = []
        self.win_trades = []
        self.loss_trades = []
        self.ping_trades = []
        self.max_reabck = 0
        self.mean_win = 0
        self.mean_loss = 0
        self.max_win = 0
        self.min_loss = 0
        self.std_win = 0
        self.std_loss = 0
        self.var_win = 0
        self.var_loss = 0
        self.start_date = None
        self.yj_pct = yj_pct
        self.total_yongjin = 0

    def recover_from_db(self):
        r = mongoManager.db[mongoManager.COL_POSITION_RECORD].find_one({'strategy_name': self.strategy_name})
        if r:
            for k, v in r.items():
                if hasattr(self, k):
                    setattr(self,k,v)
        stks = mongoManager.db[mongoManager.COL_AI_TRADE_HIS].find({'owner_strategy': self.strategy_name, 'state': '持仓'})
        if stks.count()>0:
            for stk in stks:
                ownstock = MyOwnStock()
                for k, v in stk.items():
                    if hasattr(ownstock, k):
                        setattr(ownstock, k, v)
                self.own_stock[ownstock.stk_code] = ownstock

    def have_rest_assets(self):
        return self.rest_assets>1000

    def is_own_stock(self,stkcode):
        if len(self.own_stock):
            return stkcode in self.own_stock.keys()
        return False

    def have_own_stock(self):
        return len(self.own_stock)>0

    def get_own_stock(self, stkcode):
        if self.is_own_stock(stkcode):
            return self.own_stock[stkcode]
        return None

    def get_all_own_stocks(self):
        if self.have_own_stock():
            return list(self.own_stock.keys())
        return []

    # 通用的判断，基本的判断
    def _pre_could_buy(self, date_time, stkcode, price):
        # 这里不应该对时间进行判断，应该只判断仓位是否有就好了，在外部对交易时间进行控制
        buy_count = stk_utils.get_count(price=price, total_money=self.rest_assets)
        return buy_count >= 100

    def could_buy(self,date_time, stkcode, price):
        if not self._pre_could_buy(date_time, stkcode, price):
            return False
        return True

    def _pre_could_saile(self, date_time, stkcode):
        pass

    def couldsaile(self, date_time, stkcode):
        if not stkcode in self.own_stock.keys():
            # raise KeyError('%s不在持仓记录中'%(stkcode))
            return False
        ownStock = self.own_stock[stkcode]
        buy_day = parse(ownStock.first_buy_day)
        cur_day = parse(date_time)
        delta_day = (cur_day-buy_day).days
        if delta_day < 0 :
            raise Exception('!!最新时间比买入时间小')
        return delta_day>=1

    def buy_stk(self, date_time, stkcode, name, price, trade_count=0, buy_total_money=0, pct=0 ,need_insert_db=True, reason=''):

        t_obj = parse(date_time)
        date = datetime.datetime.strftime(t_obj, '%Y-%m-%d')
        if pct < 0:
            raise Exception('买入百分比不能小于0')
        if not buy_total_money and pct !=0:
            buy_total_money = self.rest_assets * pct


        if not trade_count and not buy_total_money:
            raise Exception('买入数量或买入总额为空')
        if trade_count*price > self.rest_assets or buy_total_money>self.rest_assets:
            raise Exception('买入总额超过剩余可用资金')
        if trade_count and buy_total_money:
            raise Exception('买入数量和金额不能同时存有数据')

        elog.error('买入操作*')
        if buy_total_money and not trade_count:
            buy_count = stk_utils.get_count(price=price, total_money=buy_total_money)
        if trade_count and not buy_total_money:
            buy_count = trade_count
        if buy_count < 100:
            raise Exception('无法买入小于一手')
        buy_spend_money = buy_count * price
        buy_yongjin = buy_spend_money * self.yj_pct if buy_spend_money * self.yj_pct > 5 else 5
        buy_spend_money += buy_yongjin
        self.total_yongjin += buy_yongjin
        hisRecord = BaseTradeHis(trade_day=date, trade_time=date_time, stk_code=stkcode, stk_name=name, trade_type='买入',
                                 trade_price=price, trade_count=buy_count,
                                 total_money=buy_spend_money, operationDetail='模拟买入', trade_source='AI', yongjin=buy_yongjin)
        # elog.info(hisRecord)
        if not self.is_own_stock(stkcode):
            ownStock = MyOwnStock(owner_strategy=self.strategy_name)
        else:
            ownStock = self.own_stock[stkcode]
        ownStock.add_buy_record(hisRecord)
        self.own_stock[stkcode] = ownStock
        # 交易数量+1
        self.trade_times +=1
        self.pre_assets = self.cur_assets
        # 剩下可用的钱减去这次花的钱
        self.rest_assets -= buy_spend_money
        self.update_stk_assets(stkcode=stkcode, curprice=price,cur_date=date)

        # print(ownStock)

        print(self)
        print('MMMMMMMMMMMMMMMMMMMMXXXXXXXXXXXXXXXXXX\n')
        if need_insert_db:
            kwargs = self.to_dict()
            ownStock.update_one(strategyName=self.strategy_name, **(kwargs))


    def update_stk_assets(self,stkcode, curprice,cur_date):
        if len(self.own_stock)>0 and stkcode in self.own_stock.keys():
            ownstock = self.own_stock[stkcode]
            # 当前股最新总市值
            ownstock.update_own_stk(cur_price=curprice, cur_date=cur_date)
            print(ownstock)
            # print('MMMMMMMMMMMMMMMMMMMMXXXXXXXXXXXXXXXXXX')
        self.__update_total_assets()

    def __update_total_assets(self):
        # self.high_assets = self.cur_assets
        # self.low_assets = self.cur_assets
        self.stock_assets = 0
        if len(self.own_stock) != 0:
            for code, model in self.own_stock.items():
                self.stock_assets += model.cur_total_money
        self.cur_assets = self.rest_assets + self.stock_assets
        self.high_assets = max(self.high_assets, self.cur_assets)
        self.lastupdate_time = datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HM)
        self.low_assets = min(self.low_assets, self.cur_assets)
        self.max_reabck = min(self.max_reabck, (self.cur_assets - self.pre_assets)/self.cur_assets)



    def update_assert_result(self):
        if len(self.win_trades):
            self.mean_win = np.nanmean(self.win_trades)
            self.max_win = np.max(self.win_trades)
            self.std_win = np.std(self.win_trades)
            self.var_win = np.var(self.win_trades)
        if len(self.loss_trades):
            self.var_loss = np.var(self.loss_trades)
            self.mean_loss = np.nanmean(self.loss_trades)
            self.min_loss = np.min(self.loss_trades)
            self.std_loss = np.std(self.loss_trades)

    def saile_stk(self, date_time, stkcode, name, price,saile_count=-1,need_insert_db=True,reason='', callback=None):
        t_obj = parse(date_time)
        date = t_obj.strftime('%Y-%m-%d')
        if not stkcode in self.own_stock.keys():
            raise KeyError('%s(%s)不在持仓记录中'%(stkcode,name))

        ownStock = self.own_stock[stkcode]
        if saile_count == -1:
            saile_count = ownStock.cur_total_count
        saile_total_money = saile_count * price
        saile_yongjin = saile_total_money * self.yj_pct if saile_total_money * self.yj_pct > 5 else 5
        self.total_yongjin += saile_yongjin
        saile_total_money -= saile_yongjin
        if saile_count ==0 or saile_total_money == 0:
            raise KeyError(('%s(%s) 卖出数据或金额为0, 无法卖出，直接返回' % (stkcode, name)))
        elog.error('卖出操作*')
        # 交易数量+1
        self.trade_times += 1
        #  把现在的资金总额设置缓存到上一次，因为后面卖出之后当前的资金情况会改变
        self.pre_assets = self.cur_assets
        # 当前可用资金加上卖出的钱
        self.rest_assets += saile_total_money


        hisRecord = BaseTradeHis(trade_day=date, trade_time=date_time, stk_code=stkcode, stk_name=name, trade_type='卖出',
                           trade_price=price, trade_count=saile_count,
                           total_money=saile_total_money, operationDetail='模拟卖出', trade_source='AI', yongjin=saile_yongjin)
        # elog.info(hisRecord)
        trade_profit = (price - ownStock.trade_price) / ownStock.trade_price
        self.mark_trades.append(trade_profit)
        if ownStock.trade_price > price:
            self.loss_times += 1
            self.loss_trades.append(trade_profit)
        elif ownStock.trade_price < price:
            self.win_times += 1
            self.win_trades.append(trade_profit)
        elif ownStock.trade_price == price:
            self.ping_times += 1
            self.ping_trades.append(trade_profit)

        ownStock.pop_saile_record(hisRecord)
        print(ownStock)

        if callback:
            callback(ownStock)

        # 卖出数量刚好就是买入的数量，这个票的记录就可以从持仓中删除
        # 不能用 ownStock.cur_total_count来判断，因为已经设置为0
        if ownStock.state == '清仓':
            # ownStock.trade_type = '清仓'
            # 在这里清除掉持仓记录，防止后面重新又计算持仓数量
            self.own_stock.pop(stkcode)

        self.update_stk_assets(stkcode=stkcode, curprice=price, cur_date=date)
        # 更新交易结果
        self.update_assert_result()
        print(self)
        print('MMMMMMMMMMMMMMMMMMMMXXXXXXXXXXXXXXXXXX\n')
        if need_insert_db:
            kwargs = self.to_dict()
            ownStock.update_one(**(kwargs))


    def update_one(self):
        mongoManager.db[mongoManager.COL_POSITION_RECORD].update_one({'strategy_name':self.strategy_name},{'$set':self.to_dict()},upsert=True)

    def save_to_mongo(self):
        self.update_one()
        for code, ownstock in self.own_stock.items():
            kwargs = self.to_dict()
            ownstock.update_one(**(kwargs))

    def to_dict(self):
        v = self.__dict__.copy()
        if 'own_stock' in v.keys():
            del v['own_stock']
        return v

    def cur_own_stock(self, callback):
        callback(self.own_stock)
        pass

    def detect_result(self, strategy_name):
        # r = mongoManager.db[mongoManager.COL_POSITION_RECORD].find_one({'strategy_name':strategy_name})
        # self.__init__(**r)
        trade_profits = np.array(self.mark_trades) + 1
        max_assets = self.init_assets
        min_assets = self.init_assets
        for i in trade_profits:
            cur_assets = self.init_assets * i
            max_assets = max(max_assets, cur_assets)
            min_assets = min(min_assets, cur_assets)
            pass
        pass

    def win_pct(self):
        if self.win_times != 0 and self.loss_times != 0:
            return self.win_times/(self.win_times+self.loss_times)
        return 0

    def __str__(self):
        return '策略:%s 交易数:%d 盈利数:%d 亏损数:%d 胜率:%3.2f%% 最高资产:%d 最低资产:%d \n' \
               ' 当前资产:%f 剩余可用:%f 股票市值:%f 与刚开始金额变动:%f 与上一次操作金额变动%f 回撤:%3.2f%%' % \
               (self.strategy_name, self.trade_times, self.win_times, self.loss_times, self.win_pct()*100, self.high_assets, self.low_assets,
                self.cur_assets, self.rest_assets, self.stock_assets,self.cur_assets-self.init_assets, self.cur_assets-self.pre_assets, ((self.cur_assets-self.pre_assets)*100/self.pre_assets))



# ast = Assets(init_assets=100000, strategy_name='TEST')
# 盈利：
# 全买入全卖出 OK
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=True)
# ast.saile_stk(date_time='2017-07-11 11:30:00', stkcode='600123', name='TEST', price=13, saile_count=-1, need_insert_db=True)



# 全买入 部分卖出，不同数量 OK
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 11:30:00', stkcode='600123', name='TEST', price=13, saile_count=100, need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 13:30:00', stkcode='600123', name='TEST', price=13, saile_count=200, need_insert_db=False)

# 全买入 部分卖出，不同价格 OK
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 11:30:00', stkcode='600123', name='TEST', price=13, saile_count=100, need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 13:30:00', stkcode='600123', name='TEST', price=12, saile_count=100, need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 13:30:00', stkcode='600123', name='TEST', price=14, saile_count=100, need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 13:30:00', stkcode='600123', name='TEST', price=14, saile_count=800, need_insert_db=False)

# 分批买入 分批卖出 OK
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, trade_count=800, need_insert_db=False)
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, trade_count=800, need_insert_db=False)
# ast.saile_stk(date_time='2017-07-11 13:30:00', stkcode='600123', name='TEST', price=14, saile_count=800, need_insert_db=False)


#####################

#亏村


#####################
# 测试恢复
# ast = Assets(init_assets=100000, strategy_name='TEST')
# ast.buy_stk(date_time='2017-07-10 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=True)
# ast.saile_stk(date_time='2017-07-11 11:30:00', stkcode='600123', name='TEST', price=13, saile_count=-1, need_insert_db=True)
# ast.save_to_mongo()
#
# ast2 = Assets(init_assets=100000, strategy_name='TEST')
# ast2.recover_from_db()
pass
# ast2.buy_stk(date_time='2017-07-11 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=True)
# ast2.saile_stk(date_time='2017-07-12 11:30:00', stkcode='600123', name='TEST', price=10, saile_count=-1, need_insert_db=True)
# ast2.buy_stk(date_time='2017-07-13 11:30:00',stkcode='600123', name='TEST', price=12, buy_total_money=10000,need_insert_db=True)
# ast2.saile_stk(date_time='2017-07-14 11:30:00', stkcode='600123', name='TEST', price=13, saile_count=-1, need_insert_db=True)
# ast2.save_to_mongo()
pass

#####################
# 测试持仓结果检验
# ast = Assets(init_assets=0,strategy_name='TOP_30_MA20_T104')
# ast.recover_from_db()
# ast.detect_result('TOP_30_MA20_T104')
